package cn.org.eagle_framework.generator;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import cn.org.eagle_framework.generator.provider.db.DataSourceProvider;
import cn.org.eagle_framework.generator.provider.db.table.model.Column;
import cn.org.eagle_framework.generator.provider.db.table.model.Table;
import cn.org.eagle_framework.generator.util.AntPathMatcher;
import cn.org.eagle_framework.generator.util.BeanHelper;
import cn.org.eagle_framework.generator.util.FileHelper;
import cn.org.eagle_framework.generator.util.FreemarkerHelper;
import cn.org.eagle_framework.generator.util.GLogger;
import cn.org.eagle_framework.generator.util.GeneratorException;
import cn.org.eagle_framework.generator.util.IOHelper;
import cn.org.eagle_framework.generator.util.StringHelper;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * 代码生成器核心引擎
 * 
 * 主要提供以下两个方法供外部使用
 * 
 * <pre>
 * generateBy() 用于生成文件
 * deleteBy() 用于删除生成的文件
 * </pre>
 * 
 * @author badqiu
 * @email badqiu(a)gmail.com
 */
public class Generator implements GeneratorConstants {
	private static final String GENERATOR_INSERT_LOCATION = "generator-insert-location";
	private List templateRootDirs = new ArrayList();
	private String outRootDir;
	private boolean ignoreTemplateGenerateException = true;
	private String removeExtensions = GeneratorProperties.getProperty(
			GENERATOR_REMOVE_EXTENSIONS, ".ftl,.vm");
	private boolean isCopyBinaryFile = true;

	private String includes = GeneratorProperties
			.getProperty(GENERATOR_INCLUDES); // 需要处理的模板，使用逗号分隔符,示例值:
												// java_src/**,java_test/**
	private String excludes = GeneratorProperties
			.getProperty(GENERATOR_EXCLUDES); // 不需要处理的模板，使用逗号分隔符,示例值:
												// java_src/**,java_test/**
	private String sourceEncoding = GeneratorProperties.getProperty(
			GENERATOR_SOURCE_ENCODING, "UTF-8");
	private String outputEncoding = GeneratorProperties.getProperty(
			GENERATOR_OUTPUT_ENCODING, "UTF-8");

	public Generator() {
	}

	public void setTemplateRootDir(File templateRootDir) {
		setTemplateRootDirs(new File[] { templateRootDir });
	}

	public void setTemplateRootDirs(File... templateRootDirs) {
		this.templateRootDirs = Arrays.asList(templateRootDirs);
	}

	public void addTemplateRootDir(File f) {
		templateRootDirs.add(f);
	}

	public boolean isIgnoreTemplateGenerateException() {
		return ignoreTemplateGenerateException;
	}

	public void setIgnoreTemplateGenerateException(
			boolean ignoreTemplateGenerateException) {
		this.ignoreTemplateGenerateException = ignoreTemplateGenerateException;
	}

	public boolean isCopyBinaryFile() {
		return isCopyBinaryFile;
	}

	public void setCopyBinaryFile(boolean isCopyBinaryFile) {
		this.isCopyBinaryFile = isCopyBinaryFile;
	}

	public String getSourceEncoding() {
		return sourceEncoding;
	}

	public void setSourceEncoding(String sourceEncoding) {
		if (sourceEncoding == null)
			throw new IllegalArgumentException(
					"sourceEncoding must be not null");
		this.sourceEncoding = sourceEncoding;
	}

	public String getOutputEncoding() {
		return outputEncoding;
	}

	public void setOutputEncoding(String outputEncoding) {
		if (outputEncoding == null)
			throw new IllegalArgumentException(
					"outputEncoding must be not null");
		this.outputEncoding = outputEncoding;
	}

	public void setIncludes(String includes) {
		this.includes = includes;
	}

	/** 设置不处理的模板路径,可以使用ant类似的值,使用逗号分隔，示例值： **\*.ignore */
	public void setExcludes(String excludes) {
		this.excludes = excludes;
	}

	public void setOutRootDir(String rootDir) {
		if (rootDir == null)
			throw new IllegalArgumentException("outRootDir must be not null");
		this.outRootDir = rootDir;
	}

	public String getOutRootDir() {
		// if(outRootDir == null) throw new
		// IllegalStateException("'outRootDir' property must be not null.");
		return outRootDir;
	}

	public void setRemoveExtensions(String removeExtensions) {
		this.removeExtensions = removeExtensions;
	}

	public void deleteOutRootDir() throws IOException {
		if (StringHelper.isBlank(getOutRootDir()))
			throw new IllegalStateException(
					"'outRootDir' property must be not null.");
		GLogger.println("[delete dir]    " + getOutRootDir());
		FileHelper.deleteDirectory(new File(getOutRootDir()));
	}

	/**
	 * 生成文件
	 * 
	 * @param templateModel
	 *            生成器模板可以引用的变量
	 * @param filePathModel
	 *            文件路径可以引用的变量
	 * @throws Exception
	 */
	public Generator generateBy(Map templateModel, Map filePathModel)
			throws Exception {
		processTemplateRootDirs(templateModel, filePathModel, false);
		return this;
	}

	/**
	 * 删除生成的文件
	 * 
	 * @param templateModel
	 *            生成器模板可以引用的变量
	 * @param filePathModel
	 *            文件路径可以引用的变量
	 * @return
	 * @throws Exception
	 */
	public Generator deleteBy(Map templateModel, Map filePathModel)
			throws Exception {
		processTemplateRootDirs(templateModel, filePathModel, true);
		return this;
	}

	private void processTemplateRootDirs(Map templateModel, Map filePathModel,
			boolean isDelete) throws Exception {
		if (StringHelper.isBlank(getOutRootDir()))
			throw new IllegalStateException(
					"'outRootDir' property must be not null.");
		if (templateRootDirs.size() == 0)
			throw new IllegalStateException("'templateRootDirs' cannot empty");
		GeneratorException ge = new GeneratorException(
				"generator occer error, Generator BeanInfo:"
						+ BeanHelper.describe(this));
		for (int i = 0; i < this.templateRootDirs.size(); i++) {
			File templateRootDir = (File) templateRootDirs.get(i);
			List<Exception> exceptions = scanTemplatesAndProcess(
					templateRootDir, templateModel, filePathModel, isDelete);
			ge.addAll(exceptions);
		}
		if (!ge.exceptions.isEmpty())
			throw ge;
	}

	private List<Exception> scanTemplatesAndProcess(File templateRootDir,
			Map templateModel, Map filePathModel, boolean isDelete)
			throws Exception {
		if (templateRootDir == null)
			throw new IllegalStateException(
					"'templateRootDir' must be not null");
		GLogger.println("-------------------load template from templateRootDir = '"
				+ templateRootDir.getAbsolutePath()
				+ "' outRootDir:"
				+ new File(outRootDir).getAbsolutePath());

		List srcFiles = FileHelper.searchAllNotIgnoreFile(templateRootDir);

		List<Exception> exceptions = new ArrayList();
		for (int i = 0; i < srcFiles.size(); i++) {
			File srcFile = (File) srcFiles.get(i);
			try {
				if (isDelete) {
					new TemplateProcessor().executeDelete(templateRootDir,
							templateModel, filePathModel, srcFile);
				} else {
					new TemplateProcessor().executeGenerate(templateRootDir,
							templateModel, filePathModel, srcFile);
				}
			} catch (Exception e) {
				if (ignoreTemplateGenerateException) {
					GLogger.warn("iggnore generate error,template is:"
							+ srcFile + " cause:" + e);
					exceptions.add(e);
				} else {
					throw e;
				}
			}
		}
		return exceptions;
	}

	private class TemplateProcessor {
		private GeneratorControl gg = new GeneratorControl();

		private void executeGenerate(File templateRootDir, Map templateModel,
				Map filePathModel, File srcFile) throws SQLException,
				IOException, TemplateException {
			String templateFile = FileHelper.getRelativePath(templateRootDir,
					srcFile);
			if (GeneratorHelper.isIgnoreTemplateProcess(srcFile, templateFile,
					includes, excludes)) {
				return;
			}

			if (isCopyBinaryFile && FileHelper.isBinaryFile(srcFile)) {
				String outputFilepath = proceeForOutputFilepath(filePathModel,
						templateFile);
				GLogger.println("[copy binary file by extention] from:"
						+ srcFile + " => "
						+ new File(getOutRootDir(), outputFilepath));
				IOHelper.copyAndClose(new FileInputStream(srcFile),
						new FileOutputStream(new File(getOutRootDir(),
								outputFilepath)));
				return;
			}

			try {
				String outputFilepath = proceeForOutputFilepath(filePathModel,
						templateFile);

				initGeneratorControlProperties(srcFile, outputFilepath);
				processTemplateForGeneratorControl(templateModel, templateFile);

				if (gg.isIgnoreOutput()) {
					GLogger.println("[not generate] by gg.isIgnoreOutput()=true on template:"
							+ templateFile);
					return;
				}

				if (StringHelper.isNotBlank(gg.getOutputFile())) {
					generateNewFileOrInsertIntoFile(templateFile,
							gg.getOutputFile(), templateModel);
				}
			} catch (Exception e) {
				throw new RuntimeException(
						"generate oucur error,templateFile is:" + templateFile
								+ " => " + gg.getOutputFile() + " cause:" + e,
						e);
			}
		}

		private void executeDelete(File templateRootDir, Map templateModel,
				Map filePathModel, File srcFile) throws SQLException,
				IOException, TemplateException {
			String templateFile = FileHelper.getRelativePath(templateRootDir,
					srcFile);
			if (GeneratorHelper.isIgnoreTemplateProcess(srcFile, templateFile,
					includes, excludes)) {
				return;
			}
			String outputFilepath = proceeForOutputFilepath(filePathModel,
					templateFile);
			initGeneratorControlProperties(srcFile, outputFilepath);
			gg.deleteGeneratedFile = true;
			processTemplateForGeneratorControl(templateModel, templateFile);
			GLogger.println("[delete file] file:"
					+ new File(gg.getOutputFile()).getAbsolutePath());
			new File(gg.getOutputFile()).delete();
		}

		private void initGeneratorControlProperties(File srcFile,
				String outputFile) throws SQLException {
			gg.setSourceFile(srcFile.getAbsolutePath());
			gg.setSourceFileName(srcFile.getName());
			gg.setSourceDir(srcFile.getParent());
			gg.setOutRoot(getOutRootDir());
			gg.setOutputEncoding(outputEncoding);
			gg.setSourceEncoding(sourceEncoding);
			gg.setMergeLocation(GENERATOR_INSERT_LOCATION);
			gg.setOutputFile(outputFile);
		}

		private void processTemplateForGeneratorControl(Map templateModel,
				String templateFile) throws IOException, TemplateException {
			templateModel.put("gg", gg);
			Template template = getFreeMarkerTemplate(templateFile);
			template.process(templateModel, IOHelper.NULL_WRITER);
		}

		/** 处理文件路径的变量变成输出路径 */
		private String proceeForOutputFilepath(Map filePathModel,
				String templateFile) throws IOException {
			String outputFilePath = templateFile;

			// TODO 删除兼容性的@testExpression
			int testExpressionIndex = -1;
			if ((testExpressionIndex = templateFile.indexOf('@')) != -1) {
				outputFilePath = templateFile.substring(0, testExpressionIndex);
				String testExpressionKey = templateFile
						.substring(testExpressionIndex + 1);
				Object expressionValue = filePathModel.get(testExpressionKey);
				if (expressionValue == null) {
					System.err
							.println("[not-generate] WARN: test expression is null by key:["
									+ testExpressionKey
									+ "] on template:["
									+ templateFile + "]");
					return null;
				}
				if (!"true".equals(String.valueOf(expressionValue))) {
					GLogger.println("[not-generate]\t test expression '@"
							+ testExpressionKey + "' is false,template:"
							+ templateFile);
					return null;
				}
			}

			for (String removeExtension : removeExtensions.split(",")) {
				if (outputFilePath.endsWith(removeExtension)) {
					outputFilePath = outputFilePath.substring(0,
							outputFilePath.length() - removeExtension.length());
					break;
				}
			}
			Configuration conf = GeneratorHelper.newFreeMarkerConfiguration(
					templateRootDirs, sourceEncoding, "/filepath/processor/");
			return FreemarkerHelper.processTemplateString(outputFilePath,
					filePathModel, conf);
		}

		private Template getFreeMarkerTemplate(String templateName)
				throws IOException {
			return GeneratorHelper.newFreeMarkerConfiguration(templateRootDirs,
					sourceEncoding, templateName).getTemplate(templateName);
		}

		private void generateNewFileOrInsertIntoFile(String templateFile,
				String outputFilepath, Map templateModel) throws Exception {
			Template template = getFreeMarkerTemplate(templateFile);
			template.setOutputEncoding(gg.getOutputEncoding());

			File absoluteOutputFilePath = FileHelper
					.parentMkdir(outputFilepath);
			if (absoluteOutputFilePath.exists()) {
				StringWriter newFileContentCollector = new StringWriter();
				if (GeneratorHelper.isFoundInsertLocation(gg, template,
						templateModel, absoluteOutputFilePath,
						newFileContentCollector)) {
					GLogger.println("[insert]\t generate content into:"
							+ outputFilepath);
					IOHelper.saveFile(absoluteOutputFilePath,
							newFileContentCollector.toString(),
							gg.getOutputEncoding());
					return;
				}
			}

			if (absoluteOutputFilePath.exists() && !gg.isOverride()) {
				GLogger.println("[not generate]\t by gg.isOverride()=false and outputFile exist:"
						+ outputFilepath);
				return;
			}

			GLogger.println("[generate]\t template:" + templateFile + " ==> "
					+ outputFilepath);
			FreemarkerHelper.processTemplate(template, templateModel,
					absoluteOutputFilePath, gg.getOutputEncoding());
		}
	}

	static class GeneratorHelper {
		public static boolean isIgnoreTemplateProcess(File srcFile,
				String templateFile, String includes, String excludes) {
			if (srcFile.isDirectory() || srcFile.isHidden())
				return true;
			if (templateFile.trim().equals(""))
				return true;
			if (srcFile.getName().toLowerCase().endsWith(".include")) {
				GLogger.println("[skip]\t\t endsWith '.include' template:"
						+ templateFile);
				return true;
			}
			templateFile = templateFile.replace('\\', '/');
			for (String exclude : StringHelper.tokenizeToStringArray(excludes,
					",")) {
				if (new AntPathMatcher().match(exclude.replace('\\', '/'),
						templateFile))
					return true;
			}
			if (includes == null)
				return false;
			for (String include : StringHelper.tokenizeToStringArray(includes,
					",")) {
				if (new AntPathMatcher().match(include.replace('\\', '/'),
						templateFile))
					return false;
			}
			return true;
		}

		private static boolean isFoundInsertLocation(GeneratorControl gg,
				Template template, Map model, File outputFile,
				StringWriter newFileContent) throws IOException,
				TemplateException {
			LineNumberReader reader = new LineNumberReader(new FileReader(
					outputFile));
			String line = null;
			boolean isFoundInsertLocation = false;

			// FIXME 持续性的重复生成会导致out of memory
			PrintWriter writer = new PrintWriter(newFileContent);
			while ((line = reader.readLine()) != null) {
				writer.println(line);
				// only insert once
				if (!isFoundInsertLocation
						&& line.indexOf(gg.getMergeLocation()) >= 0) {
					template.process(model, writer);
					writer.println();
					isFoundInsertLocation = true;
				}
			}

			writer.close();
			reader.close();
			return isFoundInsertLocation;
		}

		public static Configuration newFreeMarkerConfiguration(
				List<File> templateRootDirs, String defaultEncoding,
				String templateName) throws IOException {
			Configuration conf = new Configuration();

			FileTemplateLoader[] templateLoaders = new FileTemplateLoader[templateRootDirs
					.size()];
			for (int i = 0; i < templateRootDirs.size(); i++) {
				templateLoaders[i] = new FileTemplateLoader(
						(File) templateRootDirs.get(i));
			}
			MultiTemplateLoader multiTemplateLoader = new MultiTemplateLoader(
					templateLoaders);

			conf.setTemplateLoader(multiTemplateLoader);
			conf.setNumberFormat("###############");
			conf.setBooleanFormat("true,false");
			conf.setDefaultEncoding(defaultEncoding);

			// String autoIncludes = new File(new
			// File(templateName).getParent(),"macro.include").getPath();
			// List<String> availableAutoInclude =
			// FreemarkerHelper.getAvailableAutoInclude(conf,
			// Arrays.asList("macro.include",autoIncludes));
			// conf.setAutoIncludes(availableAutoInclude);
			// GLogger.info("[set Freemarker.autoIncludes]"+availableAutoInclude+" for templateName:"+templateName);

			List<String> autoIncludes = getParentPaths(templateName,
					"macro.include");
			List<String> availableAutoInclude = FreemarkerHelper
					.getAvailableAutoInclude(conf, autoIncludes);
			conf.setAutoIncludes(availableAutoInclude);
			GLogger.trace("set Freemarker.autoIncludes:" + availableAutoInclude
					+ " for templateName:" + templateName + " autoIncludes:"
					+ autoIncludes);
			return conf;
		}

		public static List<String> getParentPaths(String templateName,
				String suffix) {
			String array[] = StringHelper.tokenizeToStringArray(templateName,
					"\\/");
			List<String> list = new ArrayList<String>();
			list.add(suffix);
			list.add(File.separator + suffix);
			String path = "";
			for (int i = 0; i < array.length; i++) {
				path = path + File.separator + array[i];
				list.add(path + File.separator + suffix);
			}
			return list;
		}
	}

	public static class GeneratorModel implements java.io.Serializable {
		public Map filePathModel;
		public Map templateModel;

		public GeneratorModel(Map templateModel, Map filePathModel) {
			this.templateModel = templateModel;

			Iterator it = templateModel.entrySet().iterator();
			System.out.println("输出开始");
			while (it.hasNext()) { 
			Map.Entry entry = (Map.Entry) it.next(); 
			//Object key = entry.getKey(); 
			//Object value = entry.getValue(); 
			//System.out.println(key+":"+value);
			} 
			System.out.println("输出结束");
			// 自定义业务
			GeneratorBiz generatorBiz = new GeneratorBiz();
			generatorBiz.dealbasepackagedir(filePathModel);
			generatorBiz.dealClassName(filePathModel,templateModel);
			generatorBiz.dealbasepackage(templateModel);
			generatorBiz.dealBaseVoName(templateModel);
			generatorBiz.dealBaseActionName(templateModel);
			generatorBiz.dealBaseServiceName(templateModel);
			generatorBiz.dealBaseDaoName(templateModel);
			generatorBiz.dealJspPath(templateModel);
			generatorBiz.dealPrefix(templateModel);
			generatorBiz.dealBizpackage(filePathModel);
			generatorBiz.dealColumnComment(this.templateModel);
			
			//自动配置spring,struts2,ibatis
			XmlBiz.appendSpringConf(generatorBiz, filePathModel.get("className").toString());
			XmlBiz.appendIbatisConf(generatorBiz, filePathModel.get("className").toString());
			XmlBiz.appendStruts2Conf(generatorBiz, filePathModel.get("className").toString());
			
			
			//自动添加数据
			DataBiz.appendMenuData(generatorBiz);
			
			this.filePathModel = filePathModel;
		}
	}
}
